package me.spring.cloud.common.components.monitor.aop;

import com.dianping.cat.Cat;
import com.dianping.cat.message.Event;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Trace;
import com.dianping.cat.message.Transaction;
import me.spring.cloud.common.components.monitor.constant.Constant;
import me.spring.cloud.common.components.monitor.properties.MonitorLevelProperties;
import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

/**
 * @author luffy
 */
@Aspect
public class CatAopAspect {

  @Autowired
  private MonitorLevelProperties monitorLevelProperties;

  @Around("@annotation(me.spring.cloud.common.components.monitor.aop.MonitorTransaction)")
  public Object monitorTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
    Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
    return this.validMonitor(method) ? this.proceedWithCatTransaction(joinPoint, method)
        : this.defaultProceed(joinPoint);
  }

  @Around("@annotation(me.spring.cloud.common.components.monitor.aop.MonitorEvent)")
  public Object monitorEvent(ProceedingJoinPoint joinPoint) throws Throwable {
    Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
    return this.validMonitor(method) ? this.proceedWithCatEvent(joinPoint, method) : this.defaultProceed(joinPoint);
  }

  @Around("@annotation(me.spring.cloud.common.components.monitor.aop.MonitorTrace)")
  public Object monitorTrace(ProceedingJoinPoint joinPoint) throws Throwable {
    Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
    return this.validMonitor(method) ? this.proceedWithCatTrace(joinPoint, method) : this.defaultProceed(joinPoint);
  }

  @Around("@annotation(me.spring.cloud.common.components.monitor.aop.MonitorMetricCount)")
  public Object monitorMetricCount(ProceedingJoinPoint joinPoint) throws Throwable {
    Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
    return this.validMonitor(method) ? this.proceedWithCatMetricCount(joinPoint, method)
        : this.defaultProceed(joinPoint);
  }

  @Around("@annotation(me.spring.cloud.common.components.monitor.aop.MonitorMetricSum)")
  public Object monitorMetricSum(ProceedingJoinPoint joinPoint) throws Throwable {
    Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
    return this.validMonitor(method) ? this.proceedWithCatMetricSum(joinPoint, method) : this.defaultProceed(joinPoint);
  }

  /**
   * 使用CAT 打点 Transaction
   */
  private Object proceedWithCatTransaction(ProceedingJoinPoint joinPoint, Method method) throws Throwable {
    MonitorTransaction monitorTransaction = method.getAnnotation(MonitorTransaction.class);
    String type =
        StringUtils.isEmpty(monitorTransaction.tranType()) ? Constant.MONITORTRANSACTION
            : monitorTransaction.tranType();
    Transaction transaction = Cat.newTransaction(type, this.getMethodName(joinPoint, method));
    return this.monitor(joinPoint, transaction);

  }

  /**
   * 使用CAT 打点 Event
   */
  private Object proceedWithCatEvent(ProceedingJoinPoint joinPoint, Method method) throws Throwable {
    MonitorEvent monitorEvent = method.getAnnotation(MonitorEvent.class);
    String type = StringUtils.isEmpty(monitorEvent.eventType()) ? Constant.MONITOREVENT : monitorEvent.eventType();
    Event event = Cat.newEvent(type, this.getMethodName(joinPoint, method));
    return this.monitor(joinPoint, event);
  }

  /**
   * 使用CAT 打点 Trace
   */
  private Object proceedWithCatTrace(ProceedingJoinPoint joinPoint, Method method) throws Throwable {
    MonitorTrace monitorTrace = method.getAnnotation(MonitorTrace.class);
    String type = StringUtils.isEmpty(monitorTrace.traceType()) ? Constant.MONITORTRACE : monitorTrace.traceType();
    Trace trace = Cat.newTrace(type, this.getMethodName(joinPoint, method));
    return this.monitor(joinPoint, trace);
  }

  /**
   * 使用CAT 打点 Metric count计数
   */
  private Object proceedWithCatMetricCount(ProceedingJoinPoint joinPoint, Method method) throws Throwable {
    MonitorMetricCount monitorMetricCount = method.getAnnotation(MonitorMetricCount.class);
    String type = this.getType(monitorMetricCount.metricType());
    Transaction transaction = Cat.newTransaction(type, monitorMetricCount.businessKey());
    Object object = null;
    try {
      Cat.logMetricForCount(monitorMetricCount.countKey(), monitorMetricCount.num());
      object = joinPoint.proceed();
      if (null != transaction) {
        transaction.setSuccessStatus();
      }
    } catch (Throwable e) {
      if (null != transaction) {
        transaction.setStatus(e);
        Cat.logError(e);
        throw e;
      }
    } finally {
      if (null != transaction) {
        transaction.complete();
      }
    }
    return object;
  }

  private Object defaultProceed(ProceedingJoinPoint joinPoint) throws Throwable {
    return joinPoint.proceed();
  }

  /**
   * 使用CAT 打点 Metric sum计数
   */
  private Object proceedWithCatMetricSum(ProceedingJoinPoint joinPoint, Method method) throws Throwable {
    MonitorMetricSum monitorMetricSum = method.getAnnotation(MonitorMetricSum.class);
    String type = this.getType(monitorMetricSum.metricType());
    Transaction transaction = Cat.newTransaction(type, monitorMetricSum.businessKey());
    Object object = null;
    try {
      Cat.logMetricForSum(monitorMetricSum.sumKey(), monitorMetricSum.num());
      object = joinPoint.proceed();
      if (null != transaction) {
        transaction.setSuccessStatus();
      }
    } catch (Throwable e) {
      if (null != transaction) {
        transaction.setStatus(e);
        Cat.logError(e);
        throw e;
      }
    } finally {
      if (null != transaction) {
        transaction.complete();
      }
    }
    return object;
  }

  private Object monitor(ProceedingJoinPoint joinPoint, Message message) throws Throwable {
    Object object = null;
    try {
      object = joinPoint.proceed();
      if (null != message) {
        message.setSuccessStatus();
      }
    } catch (Throwable e) {
      if (null != message) {
        message.setStatus(e);
        Cat.logError(e);
        throw e;
      }
    } finally {
      if (null != message) {
        message.complete();
      }
    }
    return object;
  }

  private String getMethodName(ProceedingJoinPoint joinPoint, Method method) {
    return ClassUtils.getShortName(joinPoint.getTarget().getClass()) + "." + method.getName();
  }

  private String getType(String typeValue) {
    return StringUtils.isEmpty(typeValue) ? Constant.MONITORTRACE : typeValue;
  }

  /**
   * @Return boolean
   * @Author luffy
   * @Description 验证是否需要上报监控
   * @Date 2019-07-03 15:19
   * @Param [method]
   **/
  private boolean validMonitor(Method method) {
    if (null == monitorLevelProperties || null == monitorLevelProperties.getPackages() ||
        monitorLevelProperties.getPackages().isEmpty()) {
      return false;
    }
    return monitorLevelProperties.getPackages().stream()
        .anyMatch(s -> (method.getDeclaringClass().getName() + "." + method.getName()).startsWith(s));
  }
}
